package com.hq.ims.data.utils;

import com.hq.utils.exception.CommonException;
import com.hq.utils.util.FileUtils;
import com.hq.utils.util.MimeUtils;
import com.hq.utils.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FilenameUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * lichaojie
 *
 * @ClassName ImageUtil
 * 图片裁剪、旋转工具类
 **/
@Slf4j
@Component
public class ImageUtil {

    private static final List<String> IMAGE_SUFFIX = Stream.of("jpeg", "jpg", "tiff", "png", "gif", "psd", "raw",
            "eps", "svg", "pdf", "bmp").collect(Collectors.toList());
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    // ===剪切点x坐标
    private int x;

    private int y;

    // ===剪切点宽度
    private int width;

    private int height;

    public ImageUtil() {

    }

    public ImageUtil(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    /**
     * url资源转化为file流
     *
     * @param url
     * @return
     */
    public static File urlToFile(URL url) {
        InputStream is = null;
        File file = null;
        FileOutputStream fos = null;
        try {
            file = File.createTempFile("tmp", null);
            URLConnection urlConn = null;
            urlConn = url.openConnection();
            is = urlConn.getInputStream();
            fos = new FileOutputStream(file);
            byte[] buffer = new byte[4096];
            int length;
            while ((length = is.read(buffer)) > 0) {
                fos.write(buffer, 0, length);
            }
            return file;
        } catch (IOException e) {
            return null;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * @param bufferedImage 图片
     * @param angel         旋转角度
     * @return
     */
    public static BufferedImage rotateImage(BufferedImage bufferedImage, int angel) {
        if (bufferedImage == null) {
            return null;
        }
        if (angel < 0) {
            // 将负数角度，纠正为正数角度
            angel = angel + 360;
        }
        int imageWidth = bufferedImage.getWidth(null);
        int imageHeight = bufferedImage.getHeight(null);
        // 计算重新绘制图片的尺寸
        Rectangle rectangle = calculatorRotatedSize(new Rectangle(new Dimension(imageWidth, imageHeight)), angel);
        // 获取原始图片的透明度
        int type = bufferedImage.getColorModel().getTransparency();
        BufferedImage newImage = null;
        newImage = new BufferedImage(rectangle.width, rectangle.height, type);
        Graphics2D graphics = newImage.createGraphics();
        // 平移位置
        graphics.translate((rectangle.width - imageWidth) / 2, (rectangle.height - imageHeight) / 2);
        // 旋转角度
        graphics.rotate(Math.toRadians(angel), imageWidth / 2, imageHeight / 2);
        // 绘图
        graphics.drawImage(bufferedImage, null, null);
        return newImage;
    }

    /**
     * 旋转图片
     *
     * @param image 图片
     * @param angel 旋转角度
     * @return
     */
    public static BufferedImage rotateImage(Image image, int angel) {
        if (image == null) {
            return null;
        }
        if (angel < 0) {
            // 将负数角度，纠正为正数角度
            angel = angel + 360;
        }
        int imageWidth = image.getWidth(null);
        int imageHeight = image.getHeight(null);
        Rectangle rectangle = calculatorRotatedSize(new Rectangle(new Dimension(imageWidth, imageHeight)), angel);
        BufferedImage newImage = null;
        newImage = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = newImage.createGraphics();
        // transform
        graphics.translate((rectangle.width - imageWidth) / 2, (rectangle.height - imageHeight) / 2);
        graphics.rotate(Math.toRadians(angel), imageWidth / 2, imageHeight / 2);
        graphics.drawImage(image, null, null);
        return newImage;
    }

    /**
     * 计算旋转后的尺寸
     *
     * @param src
     * @param angel
     * @return
     */
    private static Rectangle calculatorRotatedSize(Rectangle src, int angel) {
        if (angel >= 90) {
            if (angel / 90 % 2 == 1) {
                int temp = src.height;
                src.height = src.width;
                src.width = temp;
            }
            angel = angel % 90;
        }
        double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;
        double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
        double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;
        double angel_dalta_width = Math.atan((double) src.height / src.width);
        double angel_dalta_height = Math.atan((double) src.width / src.height);

        int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));
        int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));
        int des_width = src.width + len_dalta_width * 2;
        int des_height = src.height + len_dalta_height * 2;
        return new java.awt.Rectangle(new Dimension(des_width, des_height));
    }

    public static MultipartFile base64ToMultipartFile(String base64) {
        //base64编码后的图片有头信息所以要分离出来 [0]data:image/png;base64, 图片内容为索引[1]
        String[] baseStrs = base64.split(",");

        //取索引为1的元素进行处理
        byte[] byteArr = Base64.decodeBase64(baseStrs[1]);
        for (int i = 0; i < byteArr.length; ++i) {
            if (byteArr[i] < 0) {
                byteArr[i] += 256;
            }
        }
        String[] split = baseStrs[0].split(";");
        String base64FileNameSuffixText = split[0];
        String base64FileNameSuffix = split[0].substring(split[0].lastIndexOf("/") + 1, base64FileNameSuffixText.length() - 1);
        String fileName = System.currentTimeMillis() + "." + base64FileNameSuffix;
        //处理过后的数据通过Base64DecodeMultipartFile转换为MultipartFile对象
        return new CustomMultipartFile(byteArr, fileName);
    }

    /**
     * mul文件文件
     *
     * @param multipartFile 多部分文件
     * @return {@code File }
     */
    public static File mulFileToFile(MultipartFile multipartFile) {
        //保存到零时文件中
        File file = null;
        try {
            String originalFilename = multipartFile.getOriginalFilename();
            String[] filename = originalFilename.split("\\.");
            file = File.createTempFile(System.currentTimeMillis() + "", filename[1]);
            //同一个multipartFile只能调用一次transferTo
            multipartFile.transferTo(file);
            //虚拟机停止后删除临时文件
            file.deleteOnExit();
        } catch (IOException e) {
            log.error("【文件转换失败】", e);
        }
        return file;

        //保存到服务器根目录
//        //文件上传前的名称
//        String fileName = multipartFile.getOriginalFilename();
//        File file = new File(fileName);
//        OutputStream out = null;
//        try {
//            //获取文件流，以文件流的方式输出到新文件
////    InputStream in = multipartFile.getInputStream();
//            out = new FileOutputStream(file);
//            byte[] ss = multipartFile.getBytes();
//            for (int i = 0; i < ss.length; i++) {
//                out.write(ss[i]);
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        } finally {
//            if (out != null) {
//                try {
//                    out.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//        return file;
    }

    public static boolean isImage(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        String fileExtension = FileUtils.getFileExtension(originalFilename);
        fileExtension = fileExtension.toLowerCase();
        try {
            //return IMAGE_SUFFIX.contains(FilenameUtils.getExtension(originalFilename));
            return IMAGE_SUFFIX.contains(fileExtension);
        } catch (Exception e) {
            e.printStackTrace();
            throw CommonException.build("文件名格式错误   " + originalFilename);
        }
    }

    public static boolean isImage(String suffix) {
        return IMAGE_SUFFIX.contains(suffix.toLowerCase());
    }

    /**
     * 对图片裁剪，并把裁剪完蛋新图片保存 。
     */
    public BufferedImage cut(File image, String suffix) throws IOException {

        FileInputStream is = null;
        ImageInputStream iis = null;

        try {
            // 读取图片文件
            is = new FileInputStream(image);

            /*
             * 返回包含所有当前已注册 ImageReader 的 Iterator，这些 ImageReader
             * 声称能够解码指定格式。 参数：formatName - 包含非正式格式名称 .
             *（例如 "jpeg" 或 "tiff"）等 。
             */
            Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName(suffix);
            ImageReader reader = it.next();
            // 获取图片流
            iis = ImageIO.createImageInputStream(is);

            /*
             iis:读取源.true:只向前搜索 </p>.将它标记为 ‘只向前搜索'。
             * 此设置意味着包含在输入源中的图像将只按顺序读取，可能允许 reader
             * 避免缓存包含与以前已经读取的图像关联的数据的那些输入部分。
             */
            reader.setInput(iis, true);

            /*
             描述如何对流进行解码的类<p>.用于指定如何在输入时从 Java Image I/O
             * 框架的上下文中的流转换一幅图像或一组图像。用于特定图像格式的插件
             * 将从其 ImageReader 实现的 getDefaultReadParam 方法中返回
             * ImageReadParam 的实例。
             */
            ImageReadParam param = reader.getDefaultReadParam();

            /*
             * 图片裁剪区域。Rectangle 指定了坐标空间中的一个区域，通过 Rectangle 对象
             * 的左上顶点的坐标（x，y）、宽度和高度可以定义这个区域。
             */
            Rectangle rect = new Rectangle(x, y, width, height);


            // 提供一个 BufferedImage，将其用作解码像素数据的目标。
            param.setSourceRegion(rect);

            /*
             * 使用所提供的 ImageReadParam 读取通过索引 imageIndex 指定的对象，并将
             * 它作为一个完整的 BufferedImage 返回。
             */

            return reader.read(0, param);
        } finally {
            if (is != null) {
                is.close();
            }
            if (iis != null) {
                iis.close();
            }
        }
    }

    /**
     * 作物图像
     *
     * @param file   文件
     * @param x      x
     * @param y      y
     * @param width  宽度
     * @param height 高度
     * @param angle  角
     * @return {@code MultipartFile }
     * @throws IOException ioexception
     */
    public MultipartFile cropImage(MultipartFile file, int x, int y, int width, int height, int angle) throws IOException {
        ImageUtil imageUtil = new ImageUtil(x, y, width, height);
        if (!isImage(file)) {
            return null;
        }

        File srcFile;
        String fileStr = file.toString();
        String path = redisTemplate.opsForValue().get(LocalConstant.REDIS_KEY_ID_CARD + fileStr);
        if (StringUtils.isEmpty(path)) {
            //转化格式
            srcFile = mulFileToFile(file);
            //文件复用
            redisTemplate.opsForValue().set(LocalConstant.REDIS_KEY_ID_CARD + fileStr, srcFile.getPath(), 2, TimeUnit.MINUTES);
        } else {
            srcFile = new File(path);
        }

        //裁剪图片
        BufferedImage image = imageUtil.cut(srcFile, FilenameUtils.getExtension(file.getOriginalFilename()));

        //旋转图片（angle 为偏向角度， 向相反方向旋转，角度取反）
        BufferedImage rotateImage = ImageUtil.rotateImage(image, ~angle);

        //将newImage写入字节数组输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        //处理jpg图片变色问题 需要重新绘制
        BufferedImage tag;
        tag = new BufferedImage(rotateImage.getWidth(), rotateImage.getHeight(), BufferedImage.TYPE_INT_BGR);
        Graphics g = tag.getGraphics();
        g.drawImage(rotateImage, 0, 0, null); // 绘制缩小后的图
        g.dispose();
        rotateImage = tag;

        ImageIO.write(rotateImage, "jpg", byteArrayOutputStream);
        //转换为MultipartFile
        String fileName = System.currentTimeMillis() + ".jpg";

        return new CustomMultipartFile(byteArrayOutputStream.toByteArray(), fileName);

    }

    static class CustomMultipartFile implements MultipartFile {
        private final byte[] byteArr;
        private final String name;
        private final String originalFilename;
        private String fileMime;

        public CustomMultipartFile(byte[] byteArr, String name) {
            this.byteArr = byteArr;
            this.name = "file";
            this.originalFilename = name;
            String fileExtension = FileUtils.getFileExtension(name);
            fileMime = MimeUtils.getMime(fileExtension);
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getOriginalFilename() {
            return originalFilename;
        }

        @Override
        public String getContentType() {
            return fileMime;
        }

        @Override
        public boolean isEmpty() {
            return byteArr == null || byteArr.length == 0;
        }

        @Override
        public long getSize() {
            return byteArr.length;
        }

        @Override
        public byte[] getBytes() throws IOException {
            return byteArr;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(byteArr);
        }

        @Override
        public void transferTo(File dest) throws IOException, IllegalStateException {
            new FileOutputStream(dest).write(byteArr);
        }
    }
}